The DGX-505 supports chords, which are present in the recorded tracks. These chords affect the accompaniment, harmony and performance assist, and are also listed in the lyrics screen.
There is a list of the recognised chords in the manual (p.62), and most of them can also be viewed in the chord dictionary function (although some aren't). Also mentioned are the power chord accompaniment, for example C1+5 and C1+8.
There are some that the screen can display that are not listed in the manual. These are weird combinations that are indicated with a star $*$, which seems to indicate some several different unrecognised chords (the accompaniment works with it), for example trying the accompaniment with the keys C-D♭-E♭ or C-D♭-F♯.
There are also what appears to be inversions/hybrid chords/upper structure thing (I'm not a music theory expert), where a lower note is notated separately after a slash, like "C/G", which you can get
A weird thing to note is that when playing the accompaniment notes/keys are represented as C, D♭, D, E♭, E, F, F♯, G, G♯, A, B♭, B. This means the key of A♭ major is represented as G♯ major and so on. This is not a limitation for the chords data in general, as you can see these A♭ and C♯ chords in the songs.
The chords are present in the supplied MID files on the CD, which gives an opportunity to examine them.
These were already transferred to the DGX-505 flash memory. The following songs' chords looked interesting on the lyrics screen:
C A♭7 C/G A7♭9 Dm7 G7 C C♯dim7 G7/D C C♯dim7 G/D G G/B G C C♯dim7 G7 C C♯dim7 G/D G G/D D7 G7 Gaug C Csus4 C G7/D G7 C Dm C7/E C7 F C/G F G7 C Csus4 C G7/D G7 C Dm C7/E C7 F C/E F G7 C F6 Dm C
C C/B F/A Fm/A♭ C/G G7 C C G7 C C/B F/A Fm/A♭ G7 C F C/E G/B C C/B F/A Fm/A♭ C/G G7 C G7 C G7 C F C/E G/B C C/B F/A Fm/A♭ C/G G7 C F C/E G/B C C/B F/A Fm/A♭ C/G G7 C G7 C
C G G♯dim7 Am E7♭9/G♯ Am7/G F♯m7♭5 C/G G7sus4 G7 Csus4 C C G7 C G7/D C7/E F C F C G7 G♯dim7 Am F♯m7♭5 C/G G7 C A♭/C D♭/C C
In [1]:
import mido
In [5]:
silent_night = mido.MidiFile('Music/SongCollection/for_CD/SilentNt.MID')
In [6]:
silent_night
Out[6]:
In [7]:
silent_night.ticks_per_beat
Out[7]:
In [27]:
silent_metas = [m for m in silent_night.tracks[0] if m.is_meta]
In [28]:
silent_metas
Out[28]:
It seems like most of the meta messages are tempo changes, but there are two sequencer specific meta messages of interest.
In [32]:
silent_seq_spec = [m.data for m in silent_metas if m.type == 'sequencer_specific']
In [33]:
for md in silent_seq_spec:
print(" ".join(format(b, "02X") for b in md))
The chord information must not be meta messages, then. Maybe they're system exclusive...
In [35]:
silent_sysex = [m for m in silent_night.tracks[0] if m.type == 'sysex']
In [36]:
silent_sysex
Out[36]:
In [40]:
from commons.messages import controlstate
In [43]:
s = controlstate.MidiControlState()
silent_wrapped = [s.feed(m) for m in silent_sysex]
for w in silent_wrapped:
print(w)
AHA, so they do set the MIDI master volume to something else... Anyway, there are a whole bunch of unknown messages here.
In [44]:
silent_us = silent_wrapped[5:]
In [45]:
len(silent_us)
Out[45]:
In [46]:
silent_chords = "C G G♯dim7 Am E7♭9/G♯ Am7/G F♯m7♭5 C/G G7sus4 G7 Csus4 C C G7 C G7/D C7/E F C F C G7 G♯dim7 Am F♯m7♭5 C/G G7 C A♭/C D♭/C C".split()
In [47]:
len(silent_chords)
Out[47]:
In [48]:
for w, c in zip(silent_us, silent_chords):
print(w, c)
Let's try the other two.
In [51]:
america = mido.MidiFile('America.MID')
In [52]:
turkey = mido.MidiFile('Turkey.MID')
In [58]:
for x in [america, turkey]:
print(x, x.ticks_per_beat)
for m in x.tracks[0]:
if m.is_meta and m.type != 'set_tempo':
print(m)
In [59]:
america_wrapped = [s.feed(m) for m in america.tracks[0] if m.type=='sysex']
In [93]:
turkey_wrapped = [s.feed(m) for m in turkey.tracks[0] if m.type == 'sysex']
In [94]:
america_us = [w for w in america_wrapped if w.message.data[:3] == (0x43, 0x7E, 0x02)]
turkey_us = [w for w in turkey_wrapped if w.message.data[:3] == (0x43, 0x7E, 0x02)]
In [120]:
america_chords = "C A♭7 C/G A7♭9 Dm7 G7 C C♯dim7 G7/D C C♯dim7 G/D G G/B G C C♯dim7 G7 C C♯dim7 G/D G G/D D7 G7 Gaug C Csus4 C G7/D G7 C Dm C7/E C7 F C/G F G7 C Csus4 C G7/D G7 C Dm C7/E C7 F C/E F G7 C F6 Dm C".split()
turkey_chords = "C C/B F/A Fm/A♭ C/G G7 C C G7 C C/B F/A Fm/A♭ G7 C F C/E G/B C C/B F/A Fm/A♭ C/G G7 C G7 C G7 C F C/E G/B C C/B F/A Fm/A♭ C/G G7 C F C/E G/B C C/B F/A Fm/A♭ C/G G7 C G7 C".split()
In [121]:
len(america_us) == len(america_chords)
Out[121]:
In [122]:
len(turkey_us) == len(turkey_chords)
Out[122]:
In [123]:
for w, c in zip(america_us, america_chords):
print(w, c)
In [124]:
for w, c in zip(turkey_us, turkey_chords):
print(w, c)
In [126]:
def chord_assign(chord_mapping, wraps, chords, rev_mapping):
for w, c in zip(wraps, chords):
code = w.message.data[-4:]
if c in chord_mapping:
assert chord_mapping[c] == code
else:
chord_mapping[c] = code
if code in rev_mapping:
assert rev_mapping[code] == c
else:
rev_mapping[code] = c
cmap = {}
rcmap = {}
chord_assign(cmap, silent_us, silent_chords, rcmap)
chord_assign(cmap, america_us, america_chords, rcmap)
chord_assign(cmap, turkey_us, turkey_chords, rcmap)
In [127]:
def hexspace(x):
return " ".join(format(b, "02X") for b in x)
for chord, code in sorted(cmap.items()):
print(hexspace(code), chord)
It looks like the codes are of the format A B A B, where A is the root and B is the chord type. If both sets of A and B are the same, it's a straight up chord; if the second is different that means there's some sort of inversion thing happening, and the last ends in 1E
.
note | ♭ | ♮ | ♯ |
---|---|---|---|
C | ? | 00 ,31 |
41 |
D | 22 |
02 |
? |
E | ? | 04 |
? |
F | ? | 05 |
06 |
G | ? | 07 |
45 |
A | 26 |
09 |
? |
B | ? | 0B |
? |
If I were to hazard a guess at the pattern, I think there's two types going on here. First, there's the basic sequence:
code | note |
---|---|
00 |
C |
01 |
C♯/D♭ |
02 |
D |
03 |
D♯/E♭ |
04 |
E |
05 |
F |
06 |
F♯/G♭ |
07 |
G |
08 |
G♯/A♭ |
09 |
A |
0A |
A♯/B♭ |
0B |
B |
This is used for the base keys, and also F♯, but also not for the lower note if it is C.
This doesn't distinguish between enharmonic notes, so we also have:
lo | hi2 |
3 |
4 |
---|---|---|---|
1 |
C♭ | C | C♯ |
2 |
D♭ | D | D♯ |
3 |
E♭ | E | E♯ |
4 |
F♭ | F | F♯ |
5 |
G♭ | G | G♯ |
6 |
A♭ | A | A♯ |
7 |
B♭ | B | B♯ |
Hmm, how could we test this out.
In [128]:
for chord, code in sorted(cmap.items(), key=lambda x:(x[1][1], x[1][0], x[1][2], x[1][3])):
print(hexspace(code), chord)
00
: Major (C)01
: Sixth (C6)07
: Augmented (Caug)08
: Minor (Cm)0A
: Minor seventh (Cm7)0B
: Minor seventh flatted fifth (Cm7♭5)12
: Diminished seventh (Cdim7)13
: Seventh (C7)14
: Seventh suspended fourth (C7sus4)19
: Seventh flatted ninth (C7♭9)20
: Suspended fourth (Csus4)
This is the same as the PSR-225, except that these are part of SysEx messages. This would imply that you can't have the same with an open octave 1+8, which makes sense.
In [11]:
import mido
In [21]:
import glob
import os.path
In [27]:
midtracks = [(os.path.basename(fn), mido.MidiFile(fn).tracks[0]) for fn in
glob.glob(os.path.expanduser('~/Music/SongCollection/for_CD/*.MID'))]
In [32]:
def hexspace(x):
return " ".join(format(b, '02X') for b in x)
In [71]:
sysex_occurences = {}
for song, track in midtracks:
song_name = song.partition('.')[0]
for m in track:
if m.type == 'sysex':
sysex_occurences.setdefault(m.data, set()).add(song_name)
In [100]:
def ocprint(ocs, key=None):
for d in sorted(ocs, key=key):
print(hexspace(d), ','.join(sorted(ocs[d]))[:75], sep=": ")
In [84]:
seqspec_occurences = {}
for song, track in midtracks:
song_name = song.partition('.')[0]
for m in track:
if m.type == 'sequencer_specific':
seqspec_occurences.setdefault(m.data, set()).add(song_name)
In [95]:
ocprint(sysex_occurences)
In [91]:
ocprint(seqspec_occurences)
In [101]:
ocprint({k:v for k, v in sysex_occurences.items() if k[-1] == 0x1E}, key=lambda k:k[-2])
In [114]:
ocprint({k:v for k, v in sysex_occurences.items() if k[2] == 0x02 and k[4] >= 0x1E}, key=lambda k:k[4])
No odd chords.
In [113]:
ocprint({k:v for k, v in sysex_occurences.items() if k[2] == 0x02 and k[4] != k[6] and k[6] != 0x1E}, key=lambda k:k[4])
Hmmm, it looks like there are weird things in D_Weasel and D_Mondshin
In [126]:
ocprint({k:v for k, v in sysex_occurences.items() if k[2] == 0x02 and
not((0x20 <= (k[3] & 0xF0) <= 0x40) and ( 0x00 <= (k[3] & 0xF) <= 0x07))},
key=lambda k:k[3])
In [125]:
ocprint({k:v for k, v in sysex_occurences.items() if k[2] == 0x02 and
not (0x00 <= k[3] <= 0x0B)},
key=lambda k:k[3])
In [127]:
control_occurences = {}
for song, track in midtracks:
song_name = song.partition('.')[0]
for m in track:
if m.type == 'control_change':
control_occurences.setdefault(m.control, set()).add(song_name)
In [128]:
control_occurences
Out[128]:
In [130]:
from commons.messages import wrappers
In [134]:
def cwrap(c):
try:
return wrappers.Control(c)
except ValueError:
return c
[cwrap(c) for c in control_occurences]
Out[134]:
We have controls 99, 98, 94 not recognised.
In [147]:
m_occurences = {}
for song, track in midtracks:
song_name = song.partition('.')[0]
for m in track:
if m.type not in {
'note_on', 'note_off', 'pitchwheel', 'control_change', 'sysex', 'program_change', 'set_tempo',
'end_of_track', 'key_signature', 'time_signature', 'copyright', 'sequencer_specific',
}:
m_occurences.setdefault(mido.frozen.freeze_message(m.copy(time=0)), set()).add(song_name)
In [148]:
m_occurences
Out[148]:
P_Consol has a bunch of weird stuff in it.
In [ ]: